package account

import (
    "context"
    "net/http"
    "time"

    "github.com/casbin/casbin/v2"
    casbin_model "github.com/casbin/casbin/v2/model"
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
    "gorm.io/gorm"
    "iip/account/model"
    "iip/account/account_type"
    "iip/dictionary"
    "iip/idgen"
    "iip/webserver"
    "libgo/security/webpass"
    "libgo/storage/oss"
    "libgo/utils/casbin/memory_adapter"
)

type Account struct {
    cfg    *Config
    l      *zap.Logger
    logger *zap.SugaredLogger
    db     *gorm.DB
    idgen  *idgen.IDGen
    dict   *dictionary.Dictionary

    r      *gin.Engine
    websrv *webserver.WebServer
    csb    *casbin.SyncedEnforcer
    oss    *oss.MinioBucket
}

func New() *Account {
    x := &Account{}
    x.cfg = DefaultConfig()
    return x
}

func (s *Account) WithLogger(l *zap.Logger) *Account {
    s.l = l.With(zap.String("mod", "account"))
    s.logger = s.l.Sugar()
    return s
}

func (s *Account) WithConfig(cfg *Config) *Account {
    s.cfg = cfg
    return s
}

func (s *Account) WithDB(db *gorm.DB) *Account {
    s.db = db
    return s
}

func (s *Account) WithWebServer(srv *webserver.WebServer) *Account {
    s.websrv = srv
    return s
}

func (s *Account) WithIDGen(idgen *idgen.IDGen) *Account {
    s.idgen = idgen
    return s
}

func (s *Account) WithDictionary(d *dictionary.Dictionary) *Account {
    s.dict = d
    return s
}

func (s *Account) WithOSS(o *oss.MinioBucket) *Account {
    s.oss = o
    return s
}

func (s *Account) Start() {
    s.start_casbin()
    s.start_db()
    s.start_web()
}

func (s *Account) start_db() {

    if err := s.init_buildin_resources(); err != nil {
        panic(err)
    }

    if err := s.init_buildin_role(); err != nil {
        panic(err)
    }

    if err := s.init_buildin_account(); err != nil {
        panic(err)
    }
}

func (s *Account) setup_jwt() {


































    middle := s.websrv.JWT()

    middle.Authenticator = s.Authenticator


















    middle.Authorizator = s.Authorizator
    middle.LoginResponse = func(c *gin.Context, code int, token string, expire time.Time) {


        acc_p, ok := c.Get("acc")
        var detail interface{}
        if ok {
            acc := acc_p.(*model.Account)

            detail = s.account_detail(acc)
        }
        c.JSON(http.StatusOK, gin.H{
            "code":   http.StatusOK,
            "token":  token,
            "expire": expire.Format(time.RFC3339),
            "data":   detail,
        })
    }
    middle.LogoutResponse = func(c *gin.Context, code int) {

        c.JSON(http.StatusOK, gin.H{
            "code": http.StatusOK,
        })
    }
    middle.Unauthorized = func(c *gin.Context, code int, message string) {

        c.JSON(code, gin.H{
            "code":    code,
            "message": message,
        })
    }








    s.websrv.GinEngine().POST("/account/register", s.Register)
    s.websrv.GinEngine().POST("/account/wxmini_register", s.WXMiniRegister)


    s.websrv.GinEngine().POST("/account/login", middle.LoginHandler)

    auth := s.websrv.GinEngine().Group("/account/")
    auth.Use(middle.MiddlewareFunc())
    {


        auth.POST("/logout", middle.LogoutHandler)

        auth.GET("/token_refresh", middle.RefreshHandler)

        auth.GET("/hello", func(c *gin.Context) {
            c.JSON(200, gin.H{"hello": "world"})
        })

        auth.PUT("/self/password", s.ChangePassword)

        auth.PUT("/self/update", s.Update)

        auth.GET("/self", s.AccountDetailByID)
    }

    admin := s.websrv.GinEngine().Group("/sys/")
    admin.Use(middle.MiddlewareFunc())
    {

        admin.POST("/account", s.Register)

        admin.GET("/account", s.SysAccountPagedList)

        admin.PUT("/account/:id", s.SysAccountUpdate)

        admin.GET("/account/:id", s.SysAccountDetailByID)

        admin.PUT("/account/:id/role", s.SysAccountRoleSet)

        admin.PUT("/account/:id/password", s.SysChangePassword)


        admin.POST("/resource", s.ResourceCreate)
        admin.DELETE("/resource/:id", s.ResourceDelete)
        admin.GET("/resource", s.ResourcePagedList)
        admin.GET("/resource/:id", s.ResourceByID)
        admin.PUT("/resource/:id", s.ResourceUpdate)


        admin.POST("/role", s.RoleCreate)
        admin.DELETE("/role/:id", s.RoleDelete)
        admin.GET("/role", s.RolePagedList)
        admin.GET("/role/:id", s.RoleByID)
        admin.PUT("/role/:id", s.RoleUpdate)


        admin.GET("/role/:id/resource", s.RoleResourcesGet)
        admin.PUT("/role/:id/resource", s.RoleResourcesSet)


        admin.POST("/role/:id/permission", s.CasbinCreate)
        admin.GET("/role/:id/permission", s.CasbinGet)
    }






}

func (s *Account) start_web() {
    s.setup_jwt()
}

func (s *Account) start_casbin() {
    modelText := `
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[role_definition]
g = _, _

[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
`
    m, err := casbin_model.NewModelFromString(modelText)
    if err != nil {
        panic(err)
    }
    a := memory_adapter.New()

    e, err := casbin.NewSyncedEnforcer(m, a)
    if err != nil {
        panic(err)
    }
    s.csb = e




    err = s.CasbinUpdate()
    if err != nil {
        panic(err)
    }
}

func (s *Account) gorm() *gorm.DB {
    return s.db.WithContext(context.Background())
}


func (s *Account) init_buildin_account() error {
    type Row struct {
        Name     string
        Pass     string
        Phone    string
        Email    string
        RoleList []string
    }
    lst := []Row{
        {"admin", "admin", "88888888888", "admin@iip.com", []string{account_type.AdminName}},
    }

    var count int64
    if err := s.gorm().Model(&model.Account{}).Count(&count).Error; err != nil {
        s.logger.Error(err)
        return err
    }
    if count != 0 {
        return nil
    }
    for _, r := range lst {
        acc := &model.Account{
            ID:    s.idgen.NextID(),
            Name:  r.Name,
            Phone: r.Phone,
            Email: r.Email,
        }

        auth := &model.AccountAuth{}
        auth.ID = s.idgen.NextID()
        auth.AccountID = acc.ID
        auth.Type = account_type.AuthTypePhonePass
        auth.IDentifier = acc.Name
        auth.Credential = webpass.MakePasswordHash(r.Pass)


        var acc_role_lst []*model.AccountRole
        for _, role := range r.RoleList {
            if role, err := s.GetRoleByName(role); err == nil {
                acc_role := &model.AccountRole{}
                acc_role.ID = s.idgen.NextID()
                acc_role.AccountID = acc.ID
                acc_role.RoleID = role.ID
                acc_role_lst = append(acc_role_lst, acc_role)
            } else {
                s.logger.Error(err.Error())
            }
        }

        err := s.gorm().Transaction(func(tx *gorm.DB) error {
            if err := tx.Create(acc).Error; err != nil {
                return err
            }
            if err := tx.Create(auth).Error; err != nil {
                return err
            }
            for _, acc_role := range acc_role_lst {
                if err := tx.Create(acc_role).Error; err != nil {
                    return err
                }
            }
            return nil
        })
        if err != nil {
            s.logger.Error(err)
            continue
        }
    }
    return nil
}


func (s *Account) init_buildin_role() error {
    type Row struct {
        Name        string
        Description string
        Duty        string
        Resources   []string
    }

    lst := []Row{

        {account_type.AdminName, account_type.AdminName, "", []string{}},
        {"未授权用户", "未授权用户，等待管理员授权后才能使用", "", []string{}},

        {"现场调度", "现场调度", "", []string{}},
        {"生产管理", "生产管理", "", []string{}},
        {"仓管", "仓库管理", "", []string{}},
        {"财务", "财务", "", []string{}},
        {"销售", "销售", "", []string{}},
        {"司机", "司机", "", []string{"driver"}},
        {"保安", "保安", "", []string{}},
        {"平台物流负责人", "平台物流负责人", "", []string{}},
        {"物流公司负责人", "物流公司负责人", "", []string{}},
        {"外驻人员", "外驻人员", "", []string{}},
        {"执法人员", "执法人员", "", []string{}},
        {"指挥室人员", "指挥室人员", "", []string{}},

    }

    var count int64
    if err := s.gorm().Model(&model.Role{}).Count(&count).Error; err != nil {
        s.logger.Error(err)
        return err
    }
    if count != 0 {
        return nil
    }

    for _, r := range lst {
        role := &model.Role{}
        role.ID = s.idgen.NextID()
        role.Name = r.Name
        role.Description = r.Description
        role.Duty = r.Duty
        if err := s.gorm().Create(role).Error; err != nil {
            s.logger.Error(err)
            continue
        }

        err := s.gorm().Transaction(func(tx *gorm.DB) error {
            for _, res_name := range r.Resources {
                res, err := s.GetResourceByName(res_name)
                if err != nil {
                    s.logger.Error(err.Error())
                    continue
                }
                role_res := &model.RoleResource{}
                role_res.ID = s.idgen.NextID()
                role_res.RoleID = role.ID
                role_res.ResourceID = res.ID
                if err := tx.Create(role_res).Error; err != nil {
                    return err
                }
            }
            return nil
        })
        if err != nil {
            s.logger.Error(err.Error())
            return err
        }
    }
    return nil
}


func (s *Account) init_buildin_resources() error {
    full_action := "(GET)|(POST)|(UPDATE)|(PUT)"
    type Row struct {
        Name        string
        Description string
        URL         string
        Action      string
    }
    lst := []Row{


        {"location", "位置接口", "(/api/:version/location)|(/api/:version/location/*)", full_action},
        {"company", "公司接口", "(/api/:version/company)|(/api/:version/company/*)", full_action},
        {"external_project", "第三方项目接口", "(/api/:version/external_project)|(/api/:version/external_project/*)", full_action},
        {"material", "物料接口", "(/api/:version/material)|(/api/:version/material/*)", full_action},
        {"mine", "矿区接口", "(/api/:version/mine)|(/api/:version/mine/*)", full_action},
        {"factory", "加工线接口", "(/api/:version/factory)|(/api/:version/factory/*)", full_action},
        {"warehouse", "仓库接口", "(/api/:version/warehouse)|(/api/:version/warehouse/*)", full_action},
        {"driver", "司机接口", "(/api/:version/driver)|(/api/:version/driver/*)", full_action},
        {"truck", "车辆接口", "(/api/:version/truck)|(/api/:version/truck/*)", full_action},
        {"weighbridge", "地磅接口", "(/api/:version/weighbridge)|(/api/:version/weighbridge/*)", full_action},
        {"business_role", "业务角色接口", "(/api/:version/business_role)|(/api/:version/business_role/*)", full_action},
    }

    var count int64
    if err := s.gorm().Model(&model.Resource{}).Count(&count).Error; err != nil {
        s.logger.Error(err)
        return err
    }
    if count != 0 {
        return nil
    }
    for _, r := range lst {
        res := &model.Resource{
            ID:          s.idgen.NextID(),
            Name:        r.Name,
            Description: r.Description,
            URL:         r.URL,
            Method:      r.Action,
        }
        if err := s.gorm().Create(res).Error; err != nil {
            s.logger.Error(err)
            continue
        }
    }
    return nil
}
